﻿import { FBSDataContextBase, DataContextFactory } from './../domain/fbsDataContext';
import { OrderService } from './orderService';
import { Inventory, TableParty, TableGroup, Table, TableZone, Order, Cart, CartItem, TableFeeRule } from "../domain/model";
import { Guid, ObjectCopier, Tools, Logger } from "../common";
import { ServiceException } from './serviceException';
import { Transaction, EntityCopier } from "tiny-entity";
import { OrderStatusEnum } from '../domain/model/orderStatusEnum';
import { TableFeeService } from 'mpj-checkout';
import { BaseService } from "./baseService";
import { SyncLock } from '../common/syncLock';
import { ICleanService } from '../common/dataClean/cleanManager';
export class TableService extends BaseService implements ICleanService {
    ctx: FBSDataContextBase;
    toString() { return "TableService"; }
    get ServiceName() { return this.toString(); }
    private orderSvr: OrderService;
    constructor() {
        super();
        this.orderSvr = new OrderService();
    }

    async CleanData() {
        let ctx = DataContextFactory.GetDataContext();
        // 清理2天前的台次
        let twoDayBefore = this.GetTwoDayBeforeTime();
        let tablePartyIds = await ctx.TableParty.Where(x => x.status != "opening").Where(x => x.openedTime < twoDayBefore, ["twoDayBefore"], [twoDayBefore]).Select(x => x.id).ToList();
        let counter = 0;
        for (let item of tablePartyIds) {
            await ctx.Delete(item);
            counter++;
        }
        new Logger().getLogger("app").info("台次数据清理完成：共清除" + counter + "条数据。==============================");

        let result = await ctx.Table
            .Join<TableParty>(x => x.tableId, ctx.TableParty, "id")
            .Join<Order>(x => x.id, ctx.Order, "orderId", false)
            .Where(x => x.status == null)
            .ToList<{ tableparty: TableParty; desktable: Table; orders: Order; }>();

        counter = 0;
        for (let r of result) {
            if (r.tableparty && r.orders) {
                if ((r.orders.status == "closed" || r.orders.status == "merged") && r.tableparty.status != "closed") {
                    // 订单已经结算，需要关闭台次
                    r.tableparty.status = "closed";
                    r.tableparty.closedTime = new Date().getTime();
                    await ctx.Update(r.tableparty);
                    counter++;
                    new Logger().getLogger("app").info("清理异常台次：" + r.tableparty.id);
                }
            }
        }
        new Logger().getLogger("app").info("异常台次清理完成：共清理" + counter + "条数据。==============================");
    }

    async VerifyTableOrdering(tableIds: { id: string; tablePartyId: string }[], terminal: { id: string; name: string; }) {
        let ctx = DataContextFactory.GetDataContext();
        let tableList = await ctx.Table.Contains(x => x.id, tableIds.map(n => n.id)).ToList();
        let tableZoneList = await ctx.TableZone.ToList();
        tableList.filter(table => {
            if (table.zone && table.zone.id != "null") {
                let tableZone = tableZoneList.find(x => x.id == table.zone.id);
                table.zone = tableZone;
            }
        });
        let lockedTable = tableList.find(x => x.lock && x.lock.terminalId != terminal.id);
        if (lockedTable) throw new ServiceException(this, this.GetLockErrText(lockedTable));

        let unOpenedTable = tableList.find(x => x.status != 'opening');
        if (unOpenedTable) throw new ServiceException(this, "该台桌已被关台，无法下单！");

        let result: { table: Table; tableParty: TableParty; }[] = [];

        for (let tableId of tableIds) {
            let tbp = await this.GetCurrentTableParty(tableId.id);
            if (tbp.status == "presettled") throw new ServiceException(this, '该台桌正在结算，无法下单！');
            if (tableId.tablePartyId && tbp.id != tableId.tablePartyId) throw new ServiceException(this, '该台桌已被操作，请撤消当前订单重新下单');

            result.push({
                table: tableList.find(x => x.id == tableId.id),
                tableParty: tbp
            });
        }

        return result;
    }

    async GetTableList(params: TableQueryParameters) {
        let ctx = DataContextFactory.GetDataContext();
        let q = ctx.Table;
        if (params.TableZoneId) {
            // 临时方案，同步字段加上后恢复。
            //q = q.Where(x => x.zone.id == params.TableZoneId, ["params.TableZoneId"], [params.TableZoneId]) as any;
        }
        if (params.TableStatus) {
            q = q.Where(x => x.status == params.TableStatus, ["params.TableStatus"], [params.TableStatus]) as any;
        }
        let tableList = await q.ToList();

        let r: { desktable: Table; tableparty: TableParty; }[] = [];

        let tablePartyList = await ctx.TableParty.Where(x => x.status != "closed").ToList();
        tablePartyList = tablePartyList.sort((a, b) => {
            return b.openedTime - a.openedTime;
        });

        for (let item of tableList) {
            let rItem: any = {};
            if (item.status == "opening") {
                rItem.tableparty = tablePartyList.find(x => x.tableId == item.id);
            }
            rItem.desktable = item;
            r.push(rItem);
        }

        return r;
    }

    async GetCurrentTableParty(tableId: string) {
        let ctx = DataContextFactory.GetDataContext();
        let list = await ctx.TableParty.Where(x => x.tableId == tableId, ["tableId"], [tableId]).OrderByDesc(x => x.openedTime).Take(1).ToList();
        let result: TableParty;
        if (list.length > 0) {
            result = list[0];
        }
        return result;
    }

    async GetTableZoneList() {
        let ctx = DataContextFactory.GetDataContext();
        return ctx.TableZone.ToList();
    }

    async GetTableById(tableId: string) {
        let ctx = DataContextFactory.GetDataContext();
        let table = await ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
        if (table.zone && table.zone.id != "null") {
            let tableZone = await ctx.TableZone.First(x => x.id == table.zone.id, ["table.zone.id"], [table.zone.id]);
            table.zone = tableZone;
        }
        return table;
    }


    async GetTablesByGroupId(tableGroupId: string, isOpening?: boolean) {
        if (tableGroupId == null || tableGroupId == undefined || tableGroupId == "") throw new ServiceException(this, "未传入分组编号")
        let ctx = DataContextFactory.GetDataContext();
        let list;
        if (isOpening == null || isOpening == undefined) {
            isOpening = true;
        }

        let result: Table[] = [];
        let q = ctx.TableParty
            .Join<Table>(x => x.id, ctx.Table, "tableId", true)
            .Join<Order>(x => x.id, ctx.Order, "orderId", true)
            .Where(x => x.tableGroupId == tableGroupId, ["tableGroupId"], [tableGroupId]);

        if (isOpening) {
            q = q.Where(x => x.status == "opening" || x.status == "presettled");
        }
        let r = await q.ToList<{ tableparty: TableParty; desktable: Table; orders: Order; }>();
        result = r.map(x => {
            let item = x.desktable;
            (<any>item).tableParty = x.tableparty;
            (<any>item).masterOrder = x.orders;
            return item;
        })
        return result;
    }

    @Transaction(DataContextFactory.GetDataContext())
    async updateTableInfo(params: UpdateTableInfoParamters) {
        // 更新台桌人数
        if (params.headCount) {
            if (params.headCount <= 0) throw new ServiceException(this, "台桌人数不可为0");
            if (!params.masterOrderId) throw new ServiceException(this, "未传入订单编号");
            let masterOrder = await this.orderSvr.GetOrderById(params.masterOrderId);
            if (!masterOrder) throw new ServiceException(this, "错误的订单编号");
            masterOrder.extended.headCount = params.headCount;
            await this.orderSvr.UpdateMasterOrder(masterOrder);
        }

        // 更新服务员
        if (!params.tablePartyId) throw new ServiceException(this, "请传入正确的台桌服务编号");
        let tableParty = await this.ctx.TableParty.First(x => x.id == params.tablePartyId, ["params.tablePartyId"], [params.tablePartyId]);
        if (!tableParty) throw new ServiceException(this, "台桌服务不存在");
        if (params.waiter) tableParty.waiter = params.waiter;
        if (params.headCount) tableParty.extended.headCount = params.headCount;
        this.ctx.Update(tableParty);

        let masterOrder = await this.ctx.Order.First(x => x.id == tableParty.orderId, ["tableParty.orderId"], [tableParty.orderId]);
        if (masterOrder && masterOrder.extended) {
            masterOrder.extended.tableParty = tableParty;
            if (params.waiter) masterOrder.extended.waiter = params.waiter;
            this.ctx.Update(masterOrder);
        }
    }

    @SyncLock()
    @Transaction(DataContextFactory.GetDataContext())
    async OpenTable(params: OpenTableParameters) {
        return this.MergeOpenTable(params);
    }
    /**
     * 开台或者并台开台
     * @param  {Table} table
     * @param  {string} tableGroupId? 并台的台桌组Id
     */
    private async MergeOpenTable(params: OpenTableParameters) {
        let headCount: number = (<any>(params.table)).headCount; // 开台的人数
        let currentTable = await this.ctx.Table.First(x => x.id == params.table.id, ["params.table.id"], [params.table.id]);
        if (!currentTable) throw new ServiceException(this, "不存在的台桌！");
        if (currentTable.status && currentTable.status == "cleaning")
            throw new ServiceException(this, "该台桌正在清台中,清台完成后，才可开台。")
        if (currentTable.status && currentTable.status == "opening")
            throw new ServiceException(this, "该台桌已经开台！");
        let tbp = this.GenerateTableParty({
            table: currentTable,
            tableGroupId: params.tableGroupId,
            waiter: params.waiter,
            headCount: headCount,
            user: params.masterOrder.creator
        });

        let tb = new Table();
        tb = tb.clone(currentTable, tb);
        tb.status = "opening";
        tb.lock = "";

        await this.ctx.Update(tb);


        //构造一个台桌主订单台桌信息
        params.masterOrder.extended = {
            table: <any>{ id: tb.id, name: tb.name },
            tableParty: tbp,
            headCount: headCount
        };
        if (params.waiter) params.masterOrder.extended.waiter = params.waiter;

        params.masterOrder = await this.orderSvr.CreateMasterOrder(params.masterOrder);

        tbp.orderId = params.masterOrder.id;
        tbp.tableName = tb.name;

        await this.ctx.Create(tbp);

        return {
            table: tb,
            tableParty: tbp,
            masterOrder: params.masterOrder
        }
    }

    private GenerateTableParty(params: GenerateTablePartyParamters): TableParty {
        let tbp = new TableParty();
        tbp.id = Guid.GetGuid();
        tbp.openedTime = new Date().getTime();
        tbp.storeId = params.table.storeId;
        tbp.status = "opening";
        tbp.tableId = params.table.id;
        tbp.tableName = params.table.name;
        tbp.tableGroupId = params.tableGroupId;
        tbp.orderId = params.orderId;
        tbp.waiter = params.waiter;
        tbp.extended = {
            creator: params.user,
            headCount: params.headCount
        };

        return tbp;
    }

    /**
     * 关闭台桌
     * 
     * @param {string} tableId 台桌Id 
     * @param {string} terminalId 关台终端号ID
     * @param {boolean} [unCheckLock] 不需要校验锁定状态，默认需要校验
     * @returns
     * 
     * @memberOf TableService
     */
    @Transaction(DataContextFactory.GetDataContext())
    // @SyncLock()
    async CloseTable(tableId: string, terminalId: string, unCheckLock?: boolean, orderStatus?: string) {
        let ctx = DataContextFactory.GetDataContext();
        let currentTable = await ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);

        if (currentTable.lock && !unCheckLock && currentTable.lock.terminalId != terminalId) {
            throw new ServiceException(this, this.GetLockErrText(currentTable));
        }

        //台桌服务
        let tp = await this.GetCurrentTableParty(tableId);
        if (!tp) {
            //无台桌服务，表示未开台
            throw new ServiceException(this, "该台桌未开台，不能关台！");
        }
        if (tp.status == "presettled") {
            throw new ServiceException(this, "该台桌正在结算，无法关台！");
        }

        let preOrderCount = await ctx.Order.Count(x => x.master == tp.orderId && (x.status == "ordered" || x.status == "settled") && x.orderType == "pre-order", ["tp.orderId"], [tp.orderId]);
        if (preOrderCount > 0 && !unCheckLock) {
            throw new ServiceException(this, "该台桌有未结算的订单，不能关台！");
        }

        tp.tableGroupId = "";
        tp.closedTime = new Date().getTime();

        if (tp.status == "cleaning") { delete tp.status }
        else { tp.status = "closed"; }

        if (currentTable.status == "cleaning") { delete currentTable.status; }
        else { currentTable.status = ""; }
        currentTable.lock = "";
        delete (<any>currentTable).tableParty;

        let masterOrder = await this.ctx.Order.First(x => x.id == tp.orderId, ["tp.orderId"], [tp.orderId]);
        if (orderStatus) {
            /// 如果是合并单则不修改值
            if (masterOrder.status != "merged") masterOrder.status = orderStatus;
        }
        else {
            masterOrder.status = "cancelled";
        }


        await this.ctx.Update(currentTable);
        await this.ctx.Update(tp);
        await this.ctx.Update(masterOrder);

        (<any>currentTable).tableParty = tp;

        let result: {
            table: Table;
            masterOrder: Order;
        } = <any>{};
        result.table = currentTable;
        result.masterOrder = masterOrder;

        return result;
    }
    /**
     * 转台
     * @param  {Table} oldTable 转台前的原始台桌
     * @param  {Table} newTable 转台后的新台桌
     */
    @Transaction(DataContextFactory.GetDataContext())
    // @SyncLock()
    async TransferTable(oldTable: Table, newTable: Table) {
        let ctx = DataContextFactory.GetDataContext();
        if (!oldTable.id) throw new ServiceException(this, "缺少oldTableId");
        if (!newTable.id) throw new ServiceException(this, "缺少newTableId");

        let oldTb = await this.ctx.Table.First(x => x.id == oldTable.id, ["oldTable.id"], [oldTable.id]);
        let newTb = await this.ctx.Table.First(x => x.id == newTable.id, ["newTable.id"], [newTable.id]);

        if (oldTb.lock) throw new ServiceException(this, this.GetLockErrText(oldTb));
        if (oldTb.status != "opening") throw new ServiceException(this, "台桌【" + oldTb.name + "】未开台！");
        if (newTb.lock) throw new ServiceException(this, this.GetLockErrText(newTb));

        if (!oldTb || !newTb) throw new ServiceException(this, "不存在的台桌！");
        if (newTb.status == "opening") throw new ServiceException(this, "台桌【" + newTb.name + "】正在处理中，无法转台！");

        let oldTbParty = await this.GetCurrentTableParty(oldTb.id);
        if (!oldTbParty) throw new ServiceException(this, "该台桌未开台不能转台！");
        if (oldTbParty.status == "presettled") {
            throw new ServiceException(this, "该台桌正在结算，不能转台！");
        }
        if (newTb && newTb.status == "cleaning") {
            throw new ServiceException(this, "新台桌正在清理中，不能转台。");
        }

        //生成新台桌信息
        newTb.status = "opening";
        let newTbParty = this.GenerateTableParty({
            table: newTb,
            tableGroupId: oldTbParty.tableGroupId,
            orderId: oldTbParty.orderId,
            waiter: oldTbParty.waiter,
            headCount: oldTbParty.extended.headCount,
            user: oldTbParty.extended.creator
        });
        await this.ctx.Update(newTb);
        await this.ctx.Create(newTbParty);

        //更新台桌主订单信息
        let masterOrder = await ctx.Order.First(x => x.id == oldTbParty.orderId, ["oldTbParty.orderId"], [oldTbParty.orderId]);
        masterOrder.extended.table = newTb;
        masterOrder.extended.tableParty = newTbParty;
        await this.ctx.Update(masterOrder);

        //更新所有preOrder信息
        let preOrderList: Order[] = await ctx.Order.Where(x => x.master == masterOrder.id, ["masterOrder.id"], [masterOrder.id]).ToList();
        for (let item of preOrderList) {
            item.extended.table = newTb;
            item.extended.tableParty = newTbParty;
            await this.ctx.Update(item);
        }

        //更新老台桌的台桌信息
        oldTb.status = "";
        oldTbParty.status = "closed";
        oldTbParty.tableGroupId = "";
        oldTbParty.waiter = [];
        oldTbParty.closedTime = new Date().getTime();
        delete (<any>oldTb).tableParty;

        await this.ctx.Update(oldTb);
        await this.ctx.Update(oldTbParty);

        return {
            oldTable: oldTb,
            oldTableParty: oldTbParty,
            newTable: newTb,
            newTableParty: newTbParty,
            masterOrder: masterOrder,
            preOrderList: preOrderList
        };
    }
    /**
     * 并台
     * @param  {Table} mainTable 合并的主台桌
     * @param  {Table[]} tableList 需要并台的台桌列表（不包含主台桌）
     */
    @Transaction(DataContextFactory.GetDataContext())
    // @SyncLock()
    async MergeTable(mainTable: Table, tableList: Table[], masterOrder: Order) {
        mainTable = await this.GetTableById(mainTable.id);
        if (mainTable.lock) throw new ServiceException(this, this.GetLockErrText(mainTable));

        let result: {
            mainTable: Table,
            tableGroup: TableGroup,
            tablePartyList: TableParty[],
            tableList: Table[],
            masterOrderList: Order[]
        } = <any>{};

        result.tableList = [];
        result.masterOrderList = [];

        let tbpList = [];


        let mainTbp = await this.GetCurrentTableParty(mainTable.id);
        if (mainTbp.status == "presettled") {
            throw new ServiceException(this, "该台桌正在结算，不能并台！");
        }
        if (mainTable.status == "cleaning") {
            throw new ServiceException(this, "该台桌正在清台中，不能并台！");
        }

        let isCleaningTable: Table[] = [];
        isCleaningTable = tableList.filter(function (item) {
            return item.status == "cleaning";
        });

        if (isCleaningTable.length > 0) {
            throw new ServiceException(this, "有台桌正在清台中，不能并台！");
        }

        let tbGroup = new TableGroup();
        tbGroup.storeId = masterOrder.storeId;
        if (mainTbp.tableGroupId) {
            //alreay merge table
            tbGroup.id = mainTbp.tableGroupId;
        }
        else {
            //does not exist tableGroup info, create new tableGroup
            tbGroup.id = Guid.GetGuid();
            await this.ctx.Create(tbGroup);

            //update main table , tableParty's tableGroupId property
            mainTbp.tableGroupId = tbGroup.id;
            await this.ctx.Update(mainTbp);
        }
        tbpList.push(mainTbp);

        masterOrder = await this.ctx.Order.First(x => x.id == mainTbp.orderId, ["mainTbp.orderId"], [mainTbp.orderId]);
        masterOrder.extended.tableParty = mainTbp;
        await this.ctx.Update(masterOrder);
        result.tableList.push(mainTable);
        result.masterOrderList.push(masterOrder);

        for (let item of tableList) {
            let tbp = await this.GetCurrentTableParty(item.id);
            if (tbp && tbp.status == "presettled") {
                throw new ServiceException(this, "该台桌正在结算，不能并台！");
            }

            if (tbp && tbp.status == "opening" && item.status == "opening") {
                let t = await this.ctx.Table.First(x => x.id == item.id, ["item.id"], [item.id]);
                if (t.lock) throw new ServiceException(this, this.GetLockErrText(t));
                if (tbp.tableGroupId) throw new ServiceException(this, "台桌【" + item.name + "】已经合并！");
                if (tbp.waiter == null || tbp.waiter == undefined || tbp.waiter.length <= 0) tbp.waiter = mainTbp.waiter;
                // 已经开台的台桌
                tbp.tableGroupId = tbGroup.id;
                await this.ctx.Update(tbp);
                tbpList.push(tbp);
            }
            else {
                //构造一个台桌主订单
                let otherTableMasterOrder = new Order();
                otherTableMasterOrder.storeId = masterOrder.storeId;
                otherTableMasterOrder.terminalName = masterOrder.terminalName;
                otherTableMasterOrder.creatorId = masterOrder.creatorId;
                otherTableMasterOrder.creator = masterOrder.creator;
                otherTableMasterOrder.serialNo = await this.orderSvr.SerialNoMaker();

                // 无台桌服务，台桌没开台
                let r = await this.MergeOpenTable({
                    table: item,
                    masterOrder: otherTableMasterOrder,
                    tableGroupId: tbGroup.id,
                    waiter: mainTbp.waiter
                })
                tbpList.push(r.tableParty);

                result.tableList.push(r.table);
                result.masterOrderList.push(r.masterOrder);
            }
        }

        result.tableGroup = tbGroup;
        result.tablePartyList = tbpList;

        return result;
    }
    /**
     * 取消并台
     * @param  {string} tableId
     */
    @Transaction(DataContextFactory.GetDataContext())
    async CancelMergeTable(tableId: string, tablePartyId: string, tableGroupId: string, masterOrderId: string, preOrderCount?: number) {
        let result: {
            tablePartyList: TableParty[];
            masterOrder: Order;
            table: Table;
        } = <any>{
            tablePartyList: [],
        };

        let ctx = DataContextFactory.GetDataContext();
        let table = await ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
        if (table.lock) throw new ServiceException(this, this.GetLockErrText(table));
        let tableParty = await ctx.TableParty.First(x => x.id == tablePartyId, ["tablePartyId"], [tablePartyId]);
        if (tableParty.status == "presettled") {
            throw new ServiceException(this, "该台桌正在结算，不能取消并台！");
        }

        let masterOrder = await ctx.Order.First(x => x.id == masterOrderId, ["masterOrderId"], [masterOrderId]);
        masterOrder.extended.tableParty.tableGroupId = "";
        tableParty.tableGroupId = "";
        if (!(preOrderCount && preOrderCount > 0)) {
            table.status = "";
            tableParty.status = "closed";
            masterOrder.status = "closed";
            delete (<any>table).tableParty;
            delete (<any>table).masterOrder;

            result.masterOrder = masterOrder;
            result.table = table;
            let currentTableParty = await this.GetCurrentTableParty(table.id);
            if (currentTableParty.id == tableParty.id) {
                await this.ctx.Update(table);
            }
        }
        result.tablePartyList.push(tableParty);
        await this.ctx.Update(tableParty);
        await this.ctx.Update(masterOrder);

        let groupTableParties = await ctx.TableParty.Where(x => x.tableGroupId == tableGroupId && x.status == "opening" && x.id != tablePartyId, ["tableGroupId", "tablePartyId"], [tableGroupId, tablePartyId]).ToList();


        if (groupTableParties.length == 1) {
            let otherTb = groupTableParties[0];
            otherTb.tableGroupId = "";
            delete (<any>otherTb).masterOrder;
            result.tablePartyList.push(otherTb);
            await this.ctx.Update(otherTb);
        }

        return result;
    }

    /**
     * 上菜
     * @param  {string} invertoryId 商品id
     * @param  {string} cartItemId 购物车项id
     * @param  {string} orderId 订单id
     * @param  {object} operator 操作人
     * @param  {number} count 上菜数量
     * @return preOrder 订单信息
     */
    async Served(invertoryId: string, cartItemId: string, orderId: string, operator: { id: string, name: string }, count: number) {
        let ctx = DataContextFactory.GetDataContext();
        let order = await ctx.Order.First(x => x.id == orderId, ["orderId"], [orderId]);
        let masterOrder = await ctx.Order.First(x => x.id == order.master, ["order.master"], [order.master]);
        for (let item of order.cart.items) {
            if (item.id == cartItemId && item.inventoryId == invertoryId) {
                //修改上菜的扩展属性
                let disQty = (item.extended.dishup && item.extended.dishup.qty) ? (item.extended.dishup.qty + count) : count;
                item.extended.dishup = {
                    operateTime: new Date(Date.now()),
                    operator: operator,
                    operatorId: operator.id,
                    qty: disQty,
                    status: item.qty == disQty ? "all" : "part"
                };

                let masterCartItem = masterOrder.cart.items.find(x => x.id == item.id);
                if (masterCartItem) masterCartItem.extended.dishup = item.extended.dishup;
                break;
            }
        }



        await ctx.Update(order);
        await ctx.Update(masterOrder);
        return order;
    }
    /**
     * 取消上菜
     * @param  {string} invertoryId
     * @param  {string} orderId
     */
    async CancelServed(invertoryId: string, orderId: string) {
        let ctx = DataContextFactory.GetDataContext();
        let order = await ctx.Order.First(x => x.id == orderId, ["orderId"], [orderId]);
        for (let item of order.cart.items) {
            if (item.inventoryId == invertoryId) {
                delete item.extended.dishup;
                break;
            }
        }
        await ctx.Update(order);

        return order;
    }

    @SyncLock()
    @Transaction(DataContextFactory.GetDataContext())
    async LockTable(tableId: string, terminal: any) {
        let ctx = DataContextFactory.GetDataContext();
        let table = await ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
        if (table.status != "opening") throw new ServiceException(this, "当前台桌未开台，无法锁台！");
        console.log(table.id, table.lock, terminal.id);
        if (table.lock && table.lock.terminalId != terminal.id) {
            throw new ServiceException(this, this.GetLockErrText(table));
        }

        let tbp = await this.GetCurrentTableParty(tableId);
        if (tbp.tableGroupId) {
            //判断台桌是否有并台，如果有锁定台桌组下面的每个台桌
            let tbs = (await ctx.TableParty
                .Join<Table>(x => x.id, ctx.Table, "tableId")
                .Where(x => x.tableGroupId == tbp.tableGroupId, ["tbp.tableGroupId"], [tbp.tableGroupId])
                .Where(x => x.status == "opening" || x.status == "presettled")
                .ToList<{ tableparty: TableParty; desktable: Table; }>()).map(x => x.desktable);
            for (let tb of tbs) {
                if (tb.status != "opening") throw new ServiceException(this, "当前台桌未开台，无法锁台！");
                tb.lock = {
                    terminalId: terminal.id,
                    terminalName: terminal.name
                };
                delete (<any>tb).tableParty;
                delete (<any>tb).masterOrder;
                this.ctx.Update(tb);
            }
        }
        else {
            table.lock = {
                terminalId: terminal.id,
                terminalName: terminal.name
            };
            ctx.Update(table);
        }
    }

    @Transaction(DataContextFactory.GetDataContext())
    async UnlockTable(tableId: string) {
        let ctx = DataContextFactory.GetDataContext();
        let table = await ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
        let tbp = await this.GetCurrentTableParty(tableId);
        if (tbp.tableGroupId) {
            //判断台桌是否有并台，如果有解除锁定台桌组下面的每个台桌
            let tbs = (await ctx.TableParty
                .Join<Table>(x => x.id, ctx.Table, "tableId")
                .Where(x => x.tableGroupId == tbp.tableGroupId, ["tbp.tableGroupId"], [tbp.tableGroupId])
                .ToList<{ tableparty: TableParty; desktable: Table; }>()).map(x => x.desktable);
            for (let tb of tbs) {
                if (tb.lock) {
                    tb.lock = "";
                    delete (<any>tb).tableParty;
                    delete (<any>tb).masterOrder;
                    await this.ctx.Update(tb);
                }
            }
        }
        else {
            if (table.lock) {
                table.lock = "";
                await this.ctx.Update(table);
            }
        }
    }

    @Transaction(DataContextFactory.GetDataContext())
    async StartCleanTable(params: StartCleanTableParamters) {
        let result = [];
        if (params.tablePartyIds.length <= 0) throw new ServiceException(this, "未传入正确的台次编号");
        // 判断清台功能是否开启
        let manualCleanTable = await this.GetConfig("pos.table.manualCleanTable");
        if (!(manualCleanTable && manualCleanTable.value.enable == true)) return result;

        if (!(params.tablePartyIds && params.tablePartyIds instanceof Array && params.tablePartyIds.length > 0)) {
            throw new ServiceException(this, "没找到有效的台次");
        }
        let rst = await this.ctx.TableParty
            .Contains(x => x.id, params.tablePartyIds)
            .Join<Table>(x => x.id, this.ctx.Table, "tableId")
            .Where(x => x.status == "closed")
            .ToList<{ tableparty: TableParty; desktable: Table; }>();
        if (rst.length <= 0) throw new ServiceException(this, "没找到有效的台次");
        for (let r of rst) {
            let tableParty = r.tableparty;

            let table = r.desktable;
            if (!(table.status == "" || table.status == null || table.status == undefined)) {
                throw new ServiceException(this, "非正常台桌状态，清台失败");
            }
            table.status = "cleaning";
            table.lock = "";
            await this.ctx.Update(table);

            tableParty.status = 'cleaning';
            await this.ctx.Update(tableParty);
            result.push(tableParty);
        }


        return result;
    }

    @Transaction(DataContextFactory.GetDataContext())
    async FinishCleanTable(params: FinishCleanTableParamters) {
        let result: {
            table: Table,
            tableParty: TableParty
        } = <any>{};
        let table = await this.ctx.Table.First(x => x.id == params.tableId && x.status == "cleaning", ["params.tableId"], [params.tableId]);
        if (!table) throw new ServiceException(this, "台桌为不可清台状态");

        let tableParty = await this.GetCurrentTableParty(table.id);
        if (!(tableParty && table && tableParty.tableId == table.id)) throw new ServiceException(this, "台次与台桌不符合");
        if (tableParty.status != "cleaning") throw new ServiceException(this, "台次为不可清台状态");


        table.status = "";
        result.table = table;
        await this.ctx.Update(table);

        tableParty.cleaningTime = new Date().getTime();
        tableParty.status = 'closed';
        if (params.bussers && params.bussers instanceof Array && params.bussers.length > 0) tableParty.busser = params.bussers;
        await this.ctx.Update(tableParty);
        result.tableParty = tableParty;
        return result;
    }

    @Transaction(DataContextFactory.GetDataContext())
    async SetTableSettled(tableId: string, checkoutOrder: Order) {
        let result = {
            tablePartyList: [],
            masterOrderList: []
        };
        if (checkoutOrder.checkout && checkoutOrder.checkout.terminal) {
            let terminalId = checkoutOrder.checkout.terminal.id;
            let terminalName = checkoutOrder.checkout.terminal.name;
            checkoutOrder.checkout.terminal = {
                id: terminalId,
                name: terminalName
            };
        }
        let table = await this.ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
        let tp = await this.GetCurrentTableParty(tableId);
        if (tp.status == "presettled") throw new ServiceException(this, "该订单已经预结算了！");
        if (tp.status == "closed" || tp.status == "cleaning") throw new ServiceException(this, "该订单已经结算了！");

        let masterOrder = await this.ctx.Order.First(x => x.id == tp.orderId, ["tp.orderId"], [tp.orderId]);
        if (masterOrder.status == "closed" || masterOrder.status == "merger") throw new ServiceException(this, "该订单已经结算了！");
        if (masterOrder.status == "cancelled") throw new ServiceException(this, "该订单已经结算取消！");

        //判断是否有并台
        if (tp.tableGroupId) {
            let refOrders = [];
            let tbs = await this.GetTablesByGroupId(tp.tableGroupId);
            for (let tb of tbs) {
                let ttp = await this.GetCurrentTableParty(tb.id);
                if (ttp.status == "presettled") throw new ServiceException(this, "该订单已经预结算了！");
                ttp.status = "presettled";

                let msOrder = await this.ctx.Order.First(x => x.id == ttp.orderId, ["ttp.orderId"], [ttp.orderId]);
                msOrder.status = "presettled";
                msOrder.refOrderId = [{ type: "sales", relation: "merger", id: checkoutOrder.id }];
                refOrders.push(
                    { type: "sales", relation: "merged-item", id: msOrder.id }
                )
                result.tablePartyList.push(ttp);
                result.masterOrderList.push(msOrder);

                await this.ctx.Update(ttp);
                await this.ctx.Update(msOrder);
            }

            let chkOrder: Order = EntityCopier.Decode(EntityCopier.Copy<Order>(checkoutOrder, new Order()));
            chkOrder.refOrderId = refOrders;
            for (let i = 0; i < chkOrder.cart.items.length; i++) {
                chkOrder.cart.items[i].inventory.pic = "";
            }
            await this.ctx.Create(chkOrder);

        }
        else {
            tp.status = "presettled";
            result.tablePartyList.push(tp);

            masterOrder.status = 'presettled';
            masterOrder.amountDue = checkoutOrder.amountDue;
            masterOrder.extended = checkoutOrder.extended;
            masterOrder.cart = checkoutOrder.cart;
            masterOrder.checkout = checkoutOrder.checkout;
            masterOrder.member = checkoutOrder.member;
            masterOrder.memberType = checkoutOrder.memberType;
            masterOrder.discount = checkoutOrder.discount;
            masterOrder.discounts = checkoutOrder.discounts;
            masterOrder.orderDiscount = checkoutOrder.orderDiscount;
            masterOrder.smallChangeOff = checkoutOrder.smallChangeOff;
            masterOrder.subtotal = checkoutOrder.subtotal;
            masterOrder.total = checkoutOrder.total;
            masterOrder.memberId = checkoutOrder.memberId;
            masterOrder.description = checkoutOrder.description;

            result.masterOrderList.push(masterOrder);

            await this.ctx.Update(tp);
            await this.ctx.Update(masterOrder);
        }

        return result;
    }

    async getTableFee(orderIds: string[]) {
        let ctx = DataContextFactory.GetDataContext();
        let totalPrice = 0;
        let inventory = null;
        let orderList = await ctx.Order.Contains(x => x.id, orderIds).ToList();
        for (let order of orderList) {
            let table = order.extended.table;
            if (!table) {
                continue;
            }
            let rst = await ctx.Table.Join(x => x.id, ctx.TableFeeRule, "feeRuleId")
                .Where(x => x.id == table.id, ["table.id"], [table.id])
                .Take(1)
                .ToList<{ desktable: Table; tablefeerules: TableFeeRule; }>();
            table = rst[0].desktable
            if (!table.feeRuleId) {
                continue;
            }
            let tableFeeRule = rst[0].tablefeerules;
            if (!tableFeeRule) {
                continue;
            }
            // 检查包间费商品是否存在，不存在则直接返回结果
            if (inventory == null) {
                inventory = await ctx.Inventory.First(x => x.id == tableFeeRule.inventoryId, ["tableFeeRule.inventoryId"], [tableFeeRule.inventoryId]);
                if (inventory == null) return null;
            }
            let price = TableFeeService.Current.WorkOutTableFee(<any>order, table, tableFeeRule);
            totalPrice += price;
        }

        if (totalPrice == 0) {
            return null;
        }

        if (inventory == null) {
            return null;
        }

        let cartItem: CartItem = <any>{};
        cartItem.id = Guid.GetGuid();
        cartItem.amount = totalPrice;
        cartItem.actualPrice = totalPrice;
        cartItem.inventory = inventory;
        cartItem.discount = 0;
        cartItem.inventoryCode = inventory.code;
        cartItem.inventoryId = inventory.id;
        cartItem.inventoryName = inventory.name;
        cartItem.isEntireDiscount = false;
        cartItem.price = totalPrice;
        cartItem.qty = 1;
        cartItem.total = totalPrice;
        (<any>cartItem).extended = {};

        let result: Cart = {
            "items": [cartItem],
            "qty": 1
        }
        return result;

    }

    @Transaction(DataContextFactory.GetDataContext())
    async CancelTableSettled(tableId: string, cancelReason: string, user, orderAmountDue: number) {
        let result = {
            tablePartyList: [],
            masterOrderList: []
        };

        cancelReason = Tools.DeleteSpecialCharacter(cancelReason);
        let table = await this.ctx.Table.First(x => x.id == tableId, ["tableId"], [tableId]);
        let tp = await this.GetCurrentTableParty(tableId);
        let masterOrder = await this.ctx.Order.First(x => x.id == tp.orderId, ["tp.orderId"], [tp.orderId]);
        //判断是否有并台
        if (tp.tableGroupId) {
            let tbs = await this.GetTablesByGroupId(tp.tableGroupId);
            let chkOrderId = masterOrder.refOrderId[0].id;
            for (let tb of tbs) {
                let ttp = await this.GetCurrentTableParty(tb.id);
                ttp.status = "opening";

                let msOrder = await this.ctx.Order.First(x => x.id == ttp.orderId, ["ttp.orderId"], [ttp.orderId]);
                msOrder.status = "ordered";
                msOrder.extended.canelPerSettled || (msOrder.extended.canelPerSettled = []);
                msOrder.extended.canelPerSettled.push({
                    operator: user,
                    operateTime: new Date().getTime(),
                    remark: cancelReason,
                    befroeAmount: msOrder.amountDue
                });
                msOrder.cancelPreSettledCount || (msOrder.cancelPreSettledCount = 0);
                msOrder.cancelPreSettledCount++;

                msOrder.refOrderId = null;
                result.tablePartyList.push(ttp);
                result.masterOrderList.push(msOrder);

                await this.ctx.Update(ttp);
                await this.ctx.Update(msOrder);
            }
            if (chkOrderId) {
                let chkOrder = new Order();
                chkOrder.id = chkOrderId;
                await this.ctx.Delete(chkOrder);
            }
        } else {
            let cartItems: CartItem[] = [];
            let perOrders = await this.ctx.Order.Where(x => x.master == masterOrder.id && x.orderType == "pre-order", ["masterOrder.id"], [masterOrder.id]).ToList();
            masterOrder.cart.qty = 0;
            for (let perOrder of perOrders) {
                for (let ci of perOrder.cart.items) {
                    cartItems.push(ci);
                }
                masterOrder.cart.qty += perOrder.cart.qty;
            }
            masterOrder.cart.items = cartItems;

            masterOrder.member = null;
            masterOrder.memberType = null;
            masterOrder.discount = 0;
            masterOrder.discounts = null;
            masterOrder.orderDiscount = null;
            masterOrder.smallChangeOff = null;
            masterOrder.memberId = null;
            masterOrder.description = null;
            masterOrder.checkout = null;

            tp.status = "opening";
            masterOrder.status = 'ordered';
            masterOrder.extended.canelPerSettled || (masterOrder.extended.canelPerSettled = []);
            masterOrder.extended.canelPerSettled.push({
                operator: user,
                operateTime: new Date().getTime(),
                remark: cancelReason,
                befroeAmount: orderAmountDue
            });
            masterOrder.cancelPreSettledCount || (masterOrder.cancelPreSettledCount = 0);
            masterOrder.cancelPreSettledCount++;

            result.tablePartyList.push(tp);
            result.masterOrderList.push(masterOrder);


            await this.ctx.Update(tp);
            await this.ctx.Update(masterOrder);
        }
        return result;
    }
}

export interface TableQueryParameters {
    TableZoneId?: string;
    TableStatus?: string;
}

export interface OpenTableParameters {
    table: Table;
    masterOrder: Order;
    tableGroupId?: string;
    waiter?: { id: string, name: string }[];
}

export interface UpdateTableInfoParamters {
    tablePartyId?: string;
    waiter?: { id: string; name: string }[],
    headCount?: number;
    masterOrderId?: string;

}

export interface GenerateTablePartyParamters {
    table: Table;
    tableGroupId?: string;
    orderId?: string;
    waiter?: { id: string; name: string }[];
    user: { id: string; name: string };
    headCount: number;
}

export interface StartCleanTableParamters {
    tablePartyIds: string[];
}

export interface FinishCleanTableParamters {
    tableId: string;
    bussers?: { id: string; name: string }[]
}